#include "sceneView.h"
#include "common_display_utils.h"
#include "model/objects/sceneObject.h"
#include "draw_utils.h"
#include "model/lighting.h"
#include "model/objectModel/image_cache.h"

//TODO REMOVER SOLO ES PARA DEBUG!!!!!!!!
//#include "geometry_debug_helper.h"
//#include <iostream>
//#include "camera/perspective_camera.h"
//////////////////////////////////////

SceneView::SceneView(const Rectangle& viewport,SceneModel& model,Fog* fog)
	: 	View(viewport),
		model(model),
		fog(fog){
	if(!fog)
		this->fog = &DayFog::instance();
	this->camera=NULL;
}

SceneView::~SceneView() {
	if(camera)
		delete camera;
}

void SceneView::addZoomFactor(float factor){
	this->camera->addZoom(factor);
}

void SceneView::setFog(Fog* fog){
	this->fog = fog;
}

void SceneView::displaySubObjects(SceneObject* obj){
	std::map<std::string,SceneObject*>::const_iterator oit = obj->beginObjects();
	for(;oit!=obj->endObjects();++oit){
		if(oit->second->beginObjects()!=oit->second->endObjects())
			displaySubObjects(oit->second);
		glPushMatrix();
			oit->second->setModelMatrix();
			oit->second->setEnv();
			renderer.render(oit->second->getObjectModel());
			oit->second->unsetEnv();
		glPopMatrix();

		castShadows(oit->second);
	}
}

void SceneView::display(){
	setEnv();

	camera->applyCamera();

	glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);
//	glEnable(GL_CULL_FACE);
//	glCullFace(GL_FRONT);

	applyWorldTransformations();

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, Lighting::instance().globalAmbient());

	drawObjects();

//	/////////////////
//
//	glDisable(GL_DEPTH_TEST);
//	PerspectiveCamera* c = dynamic_cast<PerspectiveCamera*>(camera);
//	if(c){
//		glDisable(GL_LIGHTING);
//		glColor3f(1,0,0);
//		glLineWidth(5);
//		Frustum f = c->getFrustum();
//		GeometryDebugHelper::drawFrusutm(f);
//		glEnable(GL_LIGHTING);
//	}
//	/////////////////
}

void SceneView::drawObjects(){
	const std::vector<SceneObject*> objects = model.getObjects();
	std::vector<SceneObject*>::const_iterator it=objects.begin();

	// No tiene sentido ver si es visible el skybox, siempre es visible
	SceneObject* skyBox = (*it);
	glPushMatrix();
		skyBox->setModelMatrix();
		skyBox->setEnv();
		renderer.render(skyBox->getObjectModel());
		skyBox->unsetEnv();
	glPopMatrix();

	fog->setFog();

	++it;
	SceneObject* ground = (*it);

	for(++it;it!=objects.end();++it){
		if(camera->isBoxVisible((*it)->getDimensions())){
			glPushMatrix();
				Lighting::instance().enableLights(camera->getEye(),(*it)->getDimensions().center());
				displaySubObjects(*it);
				(*it)->setEnv();
				(*it)->setModelMatrix();
				renderer.render((*it)->getObjectModel());
				(*it)->unsetEnv();
			glPopMatrix();

			castShadows(*it);
		}
	}

	Lighting::instance().disableLights();
	// Todas las pasadas que sean necesarias para activar todas las luces
	std::vector<Light*>::const_iterator lit = Lighting::instance().lights();
	unsigned int pass=0;
	for(;lit!=Lighting::instance().lights_end();){
		if(pass){
			glAlphaFunc (GL_NOTEQUAL, 0);
			glBlendFunc(GL_ONE, GL_ONE);
			glDepthFunc (GL_LEQUAL);
			glEnable(GL_BLEND);
		}
		glPushMatrix();
			if(model.getEnvironState()==SceneModel::SUNSET_ENVIRON)
				glTranslatef(0,0,-0.01f); // Para evitar problemas con las sombras

			if(Lighting::instance().enableLights(lit)){
				ground->setEnv();
				ground->setModelMatrix();
				renderer.render(ground->getObjectModel());
				ground->unsetEnv();

				pass++;
			}
		glPopMatrix();
	}
	glDisable(GL_BLEND);
}

void SceneView::renderText(const std::string& text,float x,float y,void* font){
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluOrtho2D(0.0f,1.0f,0.0f,1.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_LIGHTING);
	glDisable(GL_FOG);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_TEXTURE_2D);
	glColor3f(1.0f,1.0f,1.0f);

	glRasterPos3f(x,y, 0.0);
	glutBitmapString(font,(unsigned char*)text.c_str());
}

void SceneView::notifyActivate(){
	SceneModel::EnvironState s =  model.getEnvironState();

	if(s==SceneModel::STORM_ENVIRON)
		this->fog = &StormFog::instance();
	else
		this->fog = &DayFog::instance();
}

void SceneView::takeScreenshot(bool clean){
	// Solo la escena sin texto
	if(clean){
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		this->display();
	}

	const int width = (int)viewport.width();
	const int height = (int)viewport.height();

	const size_t bytesPerPixel = 3;	// RGB
	const size_t imageSizeInBytes = bytesPerPixel * size_t(width) * size_t(height);

	unsigned char* pixels = new unsigned char[imageSizeInBytes];

	unsigned int black_gap_size = 0;
	if(!Framework::instance().fullscreen())
		black_gap_size = 78; // Gap que aparece cuando se esta en modo ventana

	// glReadPixels can align the first pixel in each row at 1-, 2-, 4- and 8-byte boundaries. We
	// have allocated the exact size needed for the image so we have to use 1-byte alignment
	// (otherwise glReadPixels would write out of bounds)
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glReadPixels(0,-black_gap_size, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);

	// glReadPixels reads the given rectangle from bottom-left to top-right, so we must
	// reverse it
	for(int y = 0; y < height / 2; y++)
	{
		const int swapY = height - y - 1;
		for(int x = 0; x < width; x++)
		{
			const int offset = int(bytesPerPixel) * (x + y * width);
			const int swapOffset = int(bytesPerPixel) * (x + swapY * width);

			// Swap R, G and B of the 2 pixels
			std::swap(pixels[offset + 0], pixels[swapOffset + 0]);
			std::swap(pixels[offset + 1], pixels[swapOffset + 1]);
			std::swap(pixels[offset + 2], pixels[swapOffset + 2]);
		}
	}

	Texture* tex = new Texture();
	tex->ctype(Texture::RGB);
	tex->width(width);
	tex->height(height-black_gap_size);
	static unsigned int shotCount = 0;
	std::ostringstream stream;
	stream << "captura-" << ++shotCount << ".png";
	tex->name(stream.str());
	tex->data(pixels);
	ImageCache::TexturePtr ptr(tex);
	ImageCache::instance().save("./",ptr);
}

void SceneView::castShadows(SceneObject* obj){
	//cast shadows
	if(model.getEnvironState()==SceneModel::SUNSET_ENVIRON){
		glPushMatrix();
			Point lightPos = model.getEnvironLights(SceneModel::SUNSET_ENVIRON).front()->position();
			Point p = model.getGround().getDimensions().center();

			Point v = p-lightPos;
			v = v*(1/v.mod());

			// Se calcula la matriz proyeccion y se multiplica por la model-view actual
			renderer.addShadowProyection(Point(0,0,-1),p,lightPos);

			(obj)->setModelMatrix();

			glDisable(GL_LIGHTING);
			glDisable(GL_TEXTURE_2D);
			glColor3f(0.f, 0.f, 0.f);  /* shadow color */

			renderer.render((obj)->getObjectModel(),true);

			glEnable(GL_LIGHTING);
			glEnable(GL_TEXTURE_2D);
		glPopMatrix();
	}
}
